﻿using System;
using System.Collections.Generic;
using System.Data;
using System.Data.SqlClient;
using System.Text;
using System.Threading;
using System.Xml;
using Common.Utilities;
using Common.Utilities.Dispatcher;

namespace Workflows.Components.TextMining.FullTextSearch
{
	/// <summary>
	/// 
	/// </summary>
	public class SqlFullTextSearchWorker:IWorker 
	{
		#region Implementation of IWorker

		public event WorkerStarted WorkerStartedEvent;
		public event WorkerFinished WorkerFinishedEvent;
		public event WorkerHasError WorkerErredEvent;
		public event WorkerCancelled WorkerCancelledEvent;
		public event WorkerStatusChanged WorkerStatusChangedEvent;

		private Thread workerThread;
		private SqlFullTextSearchInstruction instruction;
		private SqlFullTextSearchOutput output;
		private RunContext _Context;
		private Guid _TicketID;
		private string _WorkflowName;
		private string _ComponentName;

		/// <summary>
		/// 
		/// </summary>
		public string ComponentName
		{
			get { return _ComponentName; }
		}

		/// <summary>
		/// 
		/// </summary>
		public string WorkflowName
		{
			get { return _WorkflowName; }
		}

		/// <summary></summary>
		public Thread WorkerThread
		{
			get { return workerThread; }
		}
		/// <summary></summary>
		public InstructionBase Instruction
		{
			get { return instruction; }
		}
		/// <summary></summary>
		public ResultBase Output
		{
			get { return output; }
		}
		/// <summary></summary>
		public Type ComponentType
		{
			get { return typeof(SqlFullTextSearchComponent); }
		}
		/// <summary></summary>
		public RunContext Context
		{
			get { return _Context; }
			set { _Context = value; }
		}
		/// <summary></summary>
		public void SetInstruction(Guid ticketGuid, InstructionBase request, string workflowName, string componentName)
		{
			this._TicketID = ticketGuid;
			this.instruction = (SqlFullTextSearchInstruction)request;
			this._WorkflowName = workflowName;
			this._ComponentName = componentName;
		}
		/// <summary></summary>
		public void SetInstruction(RunContext context, Guid ticketGuid, InstructionBase request, string workflowName, string componentName)
		{
			this._TicketID = ticketGuid;
			this.instruction = (SqlFullTextSearchInstruction)request;
			this._Context = context;
			this._WorkflowName = workflowName;
			this._ComponentName = componentName;
		}
		/// <summary></summary>
		public void DoWork()
		{
			if (this.WorkerStartedEvent != null)
			{
				this.WorkerStartedEvent(this._TicketID, this, this.GetType(), this.instruction);
			}
			this.workerThread = Thread.CurrentThread;
			this.DoFullTextSearch();
		}
		/// <summary></summary>
		public void DoWorkAsync()
		{
			if (this.WorkerStartedEvent != null)
			{
				this.WorkerStartedEvent(this._TicketID, this, this.GetType(), this.instruction);
			}
			this.workerThread = new Thread(new ThreadStart(this.DoFullTextSearch));
			this.workerThread.Start();
		}
		/// <summary></summary>
		public void CancelWork()
		{
			if (this.workerThread != null && this.workerThread.IsAlive)
			{
				this.workerThread.Abort();
				this.workerThread.Join();

				if (this.WorkerCancelledEvent != null)
				{
					this.WorkerCancelledEvent(this._TicketID, this.GetType(), this.instruction);
				}
			}
		}

		#endregion

		#region search
		private void DoFullTextSearch()
		{
			SqlConnection conn = new SqlConnection(this.instruction.ConnStr);
			try
			{
				foreach(string fieldName in this.instruction.TextFieldWeights.Keys)
				{
					if(!this.VerifyFieldIsFullTextIndexed(this.instruction.ConnStr,this.instruction.TableName,fieldName))
						throw new Exception(string.Format("field {0} must be full text enabled",fieldName));
				}

				conn.Open();
			    SqlCommand cmd = conn.CreateCommand();
			    cmd.CommandType = CommandType.Text;
				int recordCount = 0;
				double highScore = 0;
				double lowScore = 0;
				DateTime startTime = DateTime.Now;

				XmlDocument xDoc = new XmlDocument();
				xDoc.LoadXml("<SearchResults></SearchResults>");
				XmlNode root = xDoc.DocumentElement;
				int maxHits = 10000;
				Dictionary<int,double> hitsFinalScores=new Dictionary<int, double>();
                Dictionary<int,string> hitsTitles=new Dictionary<int, string>();

				foreach(string fieldName in this.instruction.TextFieldWeights.Keys)
				{
					List<FullTextSearchResult> hits =
						DbUtil.DoFreeTextSearch(
							this.instruction.ConnStr, this.instruction.TableName,
							this.instruction.KeyField, this.instruction.TitleField,
                            fieldName, this.instruction.Query,
							maxHits);
					if(hits!=null && hits.Count>0)
					{
					    string fieldLabel = StringUtil.RemoveNonAlphaNumericCharacters(fieldName);
                        XmlNode fieldSearchNode = XmlDataUtil.AddElement(ref xDoc, root, fieldLabel);
						XmlDataUtil.UpdateAttribute(ref xDoc, fieldSearchNode,"FieldName",fieldName);
						XmlDataUtil.UpdateAttribute(ref xDoc, fieldSearchNode,"Hits",hits.Count.ToString());
						XmlDataUtil.UpdateAttribute(ref xDoc, fieldSearchNode, "Weight",
						                            this.instruction.TextFieldWeights[fieldName].ToString());
						hits.Sort();
						foreach(FullTextSearchResult hit in hits)
						{
							XmlNode hitNode = XmlDataUtil.AddElement(ref xDoc, fieldSearchNode, "Hit");
							XmlDataUtil.UpdateAttribute(ref xDoc, hitNode,"Rank",hit.Rank.ToString());
							XmlDataUtil.UpdateAttribute(ref xDoc, hitNode,"Key",hit.Key.ToString());
                            XmlDataUtil.UpdateAttribute(ref xDoc, hitNode, "Title", hit.Title);
							XmlDataUtil.UpdateAttribute(ref xDoc, hitNode,"Score",hit.Score.ToString());
                            if(this.instruction.IncludedTextFields !=null && 
                                this.instruction.IncludedTextFields.Contains(fieldName))
                            {
                                string sql = string.Format("select {0} from {1} where {2}={3}",
                                                           fieldName, instruction.TableName, instruction.KeyField,
                                                           hit.Key);
                                cmd.CommandText = sql;
                                SqlDataReader reader = cmd.ExecuteReader();
                                if(reader.Read())
                                {
                                    string fieldContent = reader.GetString(0);
                                    if(fieldContent==null)
                                        fieldContent = string.Empty;
                                    XmlDataUtil.UpdateAttribute(ref xDoc, hitNode, "Content", fieldContent);
                                }
                                reader.Close();
                            }

							double weightedScore = this.instruction.TextFieldWeights[fieldName]*hit.Score;
							if (hitsFinalScores.ContainsKey(hit.Key))
							{
								hitsFinalScores[hit.Key] = hitsFinalScores[hit.Key] + weightedScore;
							}
							else
							{
								hitsFinalScores.Add(hit.Key, weightedScore);
							}
                            if(!hitsTitles.ContainsKey(hit.Key))
                                hitsTitles.Add(hit.Key, hit.Title);
						}
					}
				}

				XmlNode summaryNode = XmlDataUtil.AddElement(ref xDoc, root, "Summary");
				foreach(int key in hitsFinalScores.Keys)
				{
					XmlNode hitNode = XmlDataUtil.AddElement(ref xDoc, summaryNode, "Hit");
					XmlDataUtil.UpdateAttribute(ref xDoc,hitNode,"Key",key.ToString());
                    XmlDataUtil.UpdateAttribute(ref xDoc, hitNode, "Title", hitsTitles[key]);
					XmlDataUtil.UpdateAttribute(ref xDoc, hitNode,"Score",hitsFinalScores[key].ToString());
				}

				xDoc.Save(this.instruction.SearchResultFilePath);

				TimeSpan timeUsed = DateTime.Now - startTime;
				this.output=new SqlFullTextSearchOutput(
					this.instruction.OwnerComponentID,this._TicketID,
					this.instruction.SearchResultFilePath,recordCount,
					highScore,lowScore,timeUsed);
				if (this.WorkerFinishedEvent != null)
					this.WorkerFinishedEvent(this._TicketID, this.GetType(), this.instruction, this.output);
			}
			catch (Exception ex)
			{
				if (this.WorkerErredEvent != null)
					this.WorkerErredEvent(this._TicketID, this.GetType(), this.instruction, ex.ToString(), true);
			}
			finally
			{
				conn.Close();
			}
		}

		private bool VerifyFieldIsFullTextIndexed(string connStr, string tblName, string textFieldName)
		{
			if (DbUtil.IsTableFullTextIndexed(connStr, tblName))
			{
				List<string> fxColNames = DbUtil.GetFullTextIndexedColumnNames(connStr, tblName);
				if(fxColNames !=null && fxColNames.Count>0 && fxColNames.Contains(textFieldName))
					return true;
			}
			return false;
		}
		#endregion
	}
}
